package rexsee.core.transportation;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;

import rexsee.core.application.RexseeApplication;
import rexsee.core.browser.Browser;
import rexsee.core.browser.clazz.JavascriptInterface;
import rexsee.core.lang.RexseeLanguage;
import rexsee.core.utilities.Utilities;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;

public class RexseeDownload implements JavascriptInterface {

	public static final String INTERFACE_NAME = "Download";
	@Override
	public String getInterfaceName() {
		return mBrowser.application.resources.prefix + INTERFACE_NAME;
	}
	@Override
	public JavascriptInterface getInheritInterface(Browser childBrowser) {
		return new RexseeDownload(childBrowser);
	}
	@Override
	public JavascriptInterface getNewInterface(Browser childBrowser) {
		return new RexseeDownload(childBrowser);
	}

	public static final String EVENT_ONDOWNLOADPROGRESSCHANGED = "onDownloadProgressChanged";
	public static final String EVENT_ONDOWNLOADFINISHEDED = "onDownloadFinished";
	public static final String EVENT_ONDOWNLOADTIMEOUT = "onDownloadTimeout";

	public static final String DOWNLOAD_DIRECTORY = "download";

	public static final int DOWNLOAD_PROGRESS_DONE = 1;
	public static final int DOWNLOAD_PROGRESS_ONGOING = 0;
	public static final int DOWNLOAD_PROGRESS_CONNECTING = -1;
	public static final int DOWNLOAD_PROGRESS_TIMEOUT = -2;
	public static final int DOWNLOAD_PROGRESS_EXCEPTION = -3;

	protected final Context mContext;
	protected final Browser mBrowser;
	private static String syncDownloadProgress = null;

	public static abstract class DownloadProgressListener {
		public abstract void run(String url, String path, int status, long totalSize, long downloadedSize, String message, Runnable stopTheDownload);
	}

	public RexseeDownload(Browser browser) {
		super();
		mBrowser = browser;
		mContext = browser.getContext();
		mBrowser.eventList.add(EVENT_ONDOWNLOADPROGRESSCHANGED);
		mBrowser.eventList.add(EVENT_ONDOWNLOADFINISHEDED);
		mBrowser.eventList.add(EVENT_ONDOWNLOADTIMEOUT);
	}

	private static URL getDownloadURL(Browser browser, String url) {
		if (url == null) return null;
		String scheme = Uri.parse(url).getScheme();
		if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
			browser.exception(RexseeDownload.class.getName(), url + ":Invalid url scheme!");
			return null;
		}
		if (!browser.domainWhiteList.isValidUrl(url)) {
			browser.exception(RexseeDownload.class.getName(), url + ":Url not allowed.");
			return null;
		}
		try {
			return new URL(url);
		} catch (MalformedURLException e) {
			browser.exception(RexseeDownload.class.getName(), url + ":Exception on generating URL.");
			return null;
		}
	}
	private static File getDownloadFile(Browser browser, String path) {
		if (path == null) {
			browser.exception(RexseeDownload.class.getName(), "Invalid path.");
			return null;
		}
		Uri downloadPathUri = Uri.parse(path);
		String downloadPathScheme = downloadPathUri.getScheme();
		if (downloadPathScheme == null || !downloadPathScheme.equals("file")) {
			browser.exception(RexseeDownload.class.getName(), "Invalid path scheme: " + path);
			return null;
		}
		final File downloadPathFile = new File(downloadPathUri.getSchemeSpecificPart());
		if (downloadPathFile.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
			if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
				browser.exception(RexseeDownload.class.getName(), "SD card is not ready.");
				return null;
			}
		}
		File downloadPathDir = downloadPathFile.getParentFile();
		try {
			if (downloadPathDir.exists() && !downloadPathDir.isDirectory()) downloadPathDir.delete();
			if (!downloadPathDir.exists()) downloadPathDir.mkdirs();
		} catch (Exception e) {
			browser.exception(RexseeDownload.class.getName(), e);
			return null;
		}
		return downloadPathFile;
	}

	public static void download(final Browser browser, final DownloadFile downloadFile, final DownloadProgressListener progressListener) {
		if (progressListener == null) {
			browser.exception(RexseeDownload.class.getName(), "Progress listener could not be null.");
			return;
		}
		if (downloadFile == null) {
			progressListener.run(null, null, DOWNLOAD_PROGRESS_EXCEPTION, 0, 0, "Download file could not be null.", null);
			browser.exception(RexseeDownload.class.getName(), "Download file could not be null.");
			return;
		}
		final URL sourceUrl = getDownloadURL(browser, downloadFile.url);
		if (sourceUrl == null) {
			progressListener.run(null, null, DOWNLOAD_PROGRESS_EXCEPTION, 0, 0, "Invalid source url.", null);
			return;
		}
		new Thread() {
			private boolean stop = false;
			private final Runnable stopRunnable = new Runnable() {
				@Override
				public void run() {
					stop = true;
					progressListener.run(downloadFile.url, downloadFile.path, DOWNLOAD_PROGRESS_EXCEPTION, 0, 0, "User canceled.", null);
				}
			};
			@Override
			public void run() {
				int timeout = browser.application.getTimeout() * 1000;
				try {
					progressListener.run(downloadFile.url, downloadFile.path, DOWNLOAD_PROGRESS_CONNECTING, 0, 0, null, stopRunnable);
					HttpURLConnection urlConnection = (HttpURLConnection) sourceUrl.openConnection();
					CookieManager cookieManager = CookieManager.getInstance();
					String cookie = cookieManager.getCookie(downloadFile.url);
					if (cookie != null) {
						urlConnection.setRequestProperty("Cookie", cookie);
						//After urlConnection.connect(); could retrieve sessionId with urlConnection.getHeaderField("Set-Cookie");
					}
					urlConnection.setRequestProperty("User-Agent", browser.getSettings().getUserAgentString());
					urlConnection.setRequestMethod("GET");
					//urlConnection.setDoOutput(true);
					urlConnection.setConnectTimeout(timeout);
					urlConnection.setReadTimeout(timeout);
					urlConnection.connect();
					if (cookieManager.acceptCookie()) {
						////Set session cookie
						String cookieVal = null;
						String cookieKey = null;
						String sessionCookie = "";
						for (int i = 1; (cookieKey = urlConnection.getHeaderFieldKey(i)) != null; i++) {
							if (cookieKey.equalsIgnoreCase("set-cookie")) {
								cookieVal = urlConnection.getHeaderField(i);
								cookieVal = cookieVal.substring(0, cookieVal.indexOf(";"));
								sessionCookie += cookieVal + ";";
							}
						}
						if (sessionCookie != "") cookieManager.setCookie(downloadFile.url, cookieVal);
					}
					String filename = urlConnection.getHeaderField("Content-Disposition");
					if (filename != null) {
						String[] part = filename.split("filename=");
						if (part.length > 1) {
							filename = part[1].split(";")[0];
							if (!filename.trim().equals("")) {
								downloadFile.path = downloadFile.path.substring(0, downloadFile.path.lastIndexOf("/")) + "/" + filename;
							}
						}
					}
					final File targetFile = getDownloadFile(browser, downloadFile.path);
					if (targetFile == null) {
						progressListener.run(null, null, DOWNLOAD_PROGRESS_EXCEPTION, 0, 0, "Invalid target path.", null);
						return;
					}
					FileOutputStream fileOutput = new FileOutputStream(targetFile);
					InputStream inputStream = urlConnection.getInputStream();
					long totalSize = urlConnection.getContentLength();
					long downloadedSize = 0;
					byte[] buffer = new byte[1024];
					int bufferLength = 0;
					while ((bufferLength = inputStream.read(buffer)) > 0 && !stop) {
						fileOutput.write(buffer, 0, bufferLength);
						downloadedSize += bufferLength;
						progressListener.run(downloadFile.url, downloadFile.path, DOWNLOAD_PROGRESS_ONGOING, totalSize, downloadedSize, null, stopRunnable);
					}
					inputStream.close();
					fileOutput.close();
					urlConnection.disconnect();
					if (!stop) {
						progressListener.run(downloadFile.url, downloadFile.path, DOWNLOAD_PROGRESS_DONE, totalSize, downloadedSize, null, null);
						browser.eventList.run(EVENT_ONDOWNLOADFINISHEDED, new String[]{downloadFile.url, downloadFile.path});
					}
				} catch (SocketTimeoutException ste) {
					if (!stop) {
						progressListener.run(downloadFile.url, downloadFile.path, DOWNLOAD_PROGRESS_TIMEOUT, 0, 0, String.valueOf(timeout), null);
						browser.eventList.run(EVENT_ONDOWNLOADTIMEOUT, new String[]{downloadFile.url, String.valueOf(timeout)});
					}
				} catch (Exception e) {
					if (!stop) {
						progressListener.run(downloadFile.url, downloadFile.path, DOWNLOAD_PROGRESS_EXCEPTION, 0, 0, e.getLocalizedMessage(), null);
						browser.exception(RexseeDownload.class.getName(), downloadFile.url + ":" + e);
					}
				}
			}
		}.start();
	}

	public static String syncDownload(final Browser browser, DownloadFile downloadFile, final boolean cancelable, final boolean hideProgress) {
		return syncDownload(browser, downloadFile, cancelable, hideProgress, true);
	}
	public static String syncDownload(final Browser browser, DownloadFile downloadFile, final boolean cancelable, final boolean hideProgress, final boolean filenameProgress) {
		syncDownloadProgress = null;
		download(browser, downloadFile, new DownloadProgressListener() {
			@Override
			public void run(String url, String path, int status, long totalSize, long downloadedSize, String message, final Runnable stopTheDownload) {
				String pName;
				if (filenameProgress) {
					String fileName = path.substring(path.lastIndexOf("/") + 1);
					pName = RexseeLanguage.PROGRESS_DOWNLOAD + fileName + "......";
				} else {
					pName = RexseeLanguage.PROGRESS_ONGOING;
				}
				if (status == DOWNLOAD_PROGRESS_CONNECTING) {
					if (cancelable) {
						browser.progressDialog.showWithRunnable(pName + "0%", stopTheDownload);
					} else {
						browser.progressDialog.show(pName + "0%");
					}
				} else if (status == DOWNLOAD_PROGRESS_TIMEOUT) {
					syncDownloadProgress = RexseeApplication.EXCEPTION_PREFIX + message;
					if (hideProgress) browser.progressDialog.hide();
				} else if (status == DOWNLOAD_PROGRESS_EXCEPTION) {
					syncDownloadProgress = RexseeApplication.EXCEPTION_PREFIX + message;
					if (hideProgress) browser.progressDialog.hide();
				} else if (status == DOWNLOAD_PROGRESS_ONGOING) {
					String progress = (totalSize < 0) ? "" : (downloadedSize * 100 / totalSize) + "%";
					if (cancelable) {
						browser.progressDialog.showWithRunnable(pName + progress + " ", stopTheDownload);
					} else {
						browser.progressDialog.show(pName + progress + " ");
					}
				} else if (status == DOWNLOAD_PROGRESS_DONE) {
					syncDownloadProgress = "";
					if (hideProgress) browser.progressDialog.hide();
				}
			}
		});
		while (syncDownloadProgress == null) {
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
			}
		}
		if (hideProgress) browser.progressDialog.hide();
		return syncDownloadProgress;
	}
	public static String syncDownload(Browser browser, final ArrayList<DownloadFile> downloadFiles, final boolean cancelable) {
		for (int i = 0; i < downloadFiles.size(); i++) {
			String rtn = syncDownload(browser, downloadFiles.get(i), cancelable, false);
			if (!rtn.equals("")) {
				browser.progressDialog.hide();
				return rtn;
			}
		}
		browser.progressDialog.hide();
		return "";
	}

	//JavaScript Interface
	public String getDefaultDownloadPath(String url) {
		if (url.endsWith("/")) {
			mBrowser.exception(RexseeDownload.class.getName(), url + ":Error on extracting the file name from the url.");
			return null;
		}
		String fileName = url.substring(url.lastIndexOf("/") + 1);
		if (fileName == null || fileName.equals("")) {
			mBrowser.exception(RexseeDownload.class.getName(), url + ":Error on extracting the file name from the url.");
			return null;
		}
		fileName = fileName.replace("?", "_").replace("&", "_").replace("=", "_").replace(" ", "_");
		return "file://" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + DOWNLOAD_DIRECTORY + "/" + fileName;
	}

	public void downloadAndOpen(String url) {
		DownloadFile downloadFile = new DownloadFile();
		downloadFile.url = mBrowser.urlListeners.getAbsoluteUrl(url);
		downloadFile.path = getDefaultDownloadPath(downloadFile.url);
		download(mBrowser, downloadFile, new DownloadProgressListener() {
			@Override
			public void run(String url, String path, int status, long totalSize, long downloadedSize, String message, final Runnable stopTheDownload) {
				if (status == DOWNLOAD_PROGRESS_CONNECTING) {
					mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_CONNECT, stopTheDownload);
				} else if (status == DOWNLOAD_PROGRESS_TIMEOUT) {
					mBrowser.progressDialog.hide();
				} else if (status == DOWNLOAD_PROGRESS_EXCEPTION) {
					mBrowser.progressDialog.hide();
				} else if (status == DOWNLOAD_PROGRESS_ONGOING) {
					String progress = (totalSize < 0) ? "" : (downloadedSize * 100 / totalSize) + "%";
					String fileName = path.substring(path.lastIndexOf("/") + 1);
					mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_DOWNLOAD + fileName + "......" + progress + " ", stopTheDownload);
				} else if (status == DOWNLOAD_PROGRESS_DONE) {
					mBrowser.progressDialog.hide();
					mBrowser.function.load(path);
				}
			}
		});
	}
	public void download(String url) {
		download(url, true);
	}
	public void download(String url, final boolean cancelable) {
		DownloadFile downloadFile = new DownloadFile();
		downloadFile.url = mBrowser.urlListeners.getAbsoluteUrl(url);
		downloadFile.path = getDefaultDownloadPath(downloadFile.url);
		download(mBrowser, downloadFile, new DownloadProgressListener() {
			@Override
			public void run(String url, String path, int status, long totalSize, long downloadedSize, String message, final Runnable stopTheDownload) {
				if (status == DOWNLOAD_PROGRESS_CONNECTING) {
					if (cancelable) {
						mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_CONNECT, stopTheDownload);
					} else {
						mBrowser.progressDialog.show(RexseeLanguage.PROGRESS_CONNECT);
					}
				} else if (status == DOWNLOAD_PROGRESS_TIMEOUT) {
					mBrowser.progressDialog.hide();
				} else if (status == DOWNLOAD_PROGRESS_EXCEPTION) {
					mBrowser.progressDialog.hide();
				} else if (status == DOWNLOAD_PROGRESS_ONGOING) {
					String progress = (totalSize < 0) ? "" : (downloadedSize * 100 / totalSize) + "%";
					String fileName = path.substring(path.lastIndexOf("/") + 1);
					if (cancelable) {
						mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_DOWNLOAD + fileName + "......" + progress + " ", stopTheDownload);
					} else {
						mBrowser.progressDialog.show(RexseeLanguage.PROGRESS_DOWNLOAD + fileName + "......" + progress + " ");
					}
				} else if (status == DOWNLOAD_PROGRESS_DONE) {
					mBrowser.progressDialog.hide();
				}
			}
		});
	}
	public void downloadWithCallback(String url) {
		DownloadFile downloadFile = new DownloadFile();
		downloadFile.url = mBrowser.urlListeners.getAbsoluteUrl(url);
		downloadFile.path = getDefaultDownloadPath(downloadFile.url);
		download(mBrowser, downloadFile, new DownloadProgressListener() {
			@Override
			public void run(String url, String path, int status, long totalSize, long downloadedSize, String message, final Runnable stopTheDownload) {
				mBrowser.eventList.run(EVENT_ONDOWNLOADPROGRESSCHANGED, new String[]{url, path, String.valueOf(totalSize), String.valueOf(downloadedSize)});
			}
		});
	}
	public void downloadByDefault(String url) {
		Utilities.downloadByDefault(mBrowser, url);
	}

	public String syncDownloads(String urls, String path) {
		String[] urlArr = urls.split(",");
		ArrayList<DownloadFile> downloadFiles = new ArrayList<DownloadFile>();
		for (int i = 0; i < urlArr.length; i++) {
			DownloadFile file = new DownloadFile();
			file.url = mBrowser.urlListeners.getAbsoluteUrl(urlArr[i]);
			String name = file.url.substring(file.url.lastIndexOf("/") + 1);
			file.path = path + "/" + name;
			downloadFiles.add(file);
		}
		return syncDownload(mBrowser, downloadFiles, true);
	}

	public void downloadMultipleFiles(String urls, String path, final String finishCallback) {
		downloadMultipleFiles(urls, path, RexseeLanguage.PROGRESS_ONGOING + "#", finishCallback, "", false);
	}
	public void downloadMultipleFiles(String urls, String path, final String progressText, final String finishCallback, final String cancelCallback) {
		downloadMultipleFiles(urls, path, progressText, finishCallback, cancelCallback, false);
	}
	public void downloadMultipleFiles(String urls, String path, final String progressText, final String finishCallback, final String cancelCallback, final boolean forceReplace) {
		String[] urlArr = urls.split(",");
		final ArrayList<DownloadFile> downloadFiles = new ArrayList<DownloadFile>();
		for (int i = 0; i < urlArr.length; i++) {
			DownloadFile file = new DownloadFile();
			file.url = mBrowser.urlListeners.getAbsoluteUrl(urlArr[i]);
			String name = file.url.substring(file.url.lastIndexOf("/") + 1);
			file.path = path + "/" + name;
			if (!forceReplace) {
				File targetFile = new File(Uri.parse(file.path).getPath());
				if (targetFile.exists() && targetFile.isFile() && targetFile.length() > 0) continue;
			}
			downloadFiles.add(file);
		}
		final Browser browser = mBrowser;
		new Thread() {
			private boolean stop = false;
			private final Runnable stopRunnable = new Runnable() {
				@Override
				public void run() {
					stop = true;
				}
			};
			@Override
			public void run() {
				int totalFiles = downloadFiles.size();
				for (int index = 0; index < totalFiles; index++) {
					if (stop) {
						mBrowser.function.load(cancelCallback);
						return;
					}
					if (progressText != null && !progressText.trim().equalsIgnoreCase("")) {
						String pText = progressText.trim();
						if (pText.endsWith("#")) {
							pText = pText.substring(0, pText.length() - 1) + index + "/" + totalFiles;
						}
						if (cancelCallback != null && !cancelCallback.trim().equalsIgnoreCase("")) {
							browser.progressDialog.showWithRunnable(pText, stopRunnable);
						} else {
							browser.progressDialog.show(pText);
						}
					}
					DownloadFile downloadFile = downloadFiles.get(index);
					final URL sourceUrl = getDownloadURL(browser, downloadFile.url);
					if (sourceUrl == null) {
						mBrowser.exception(getInterfaceName(), "Invalid source url:" + downloadFile.url);
						continue;
					}
					int timeout = browser.application.getTimeout() * 1000;
					try {
						HttpURLConnection urlConnection = (HttpURLConnection) sourceUrl.openConnection();
						CookieManager cookieManager = CookieManager.getInstance();
						String cookie = cookieManager.getCookie(downloadFile.url);
						if (cookie != null) {
							urlConnection.setRequestProperty("Cookie", cookie);
							//After urlConnection.connect(); could retrieve sessionId with urlConnection.getHeaderField("Set-Cookie");
						}
						urlConnection.setRequestProperty("User-Agent", browser.getSettings().getUserAgentString());
						urlConnection.setRequestMethod("GET");
						//urlConnection.setDoOutput(true);
						urlConnection.setConnectTimeout(timeout);
						urlConnection.setReadTimeout(timeout);
						urlConnection.connect();
						if (cookieManager.acceptCookie()) {
							////Set session cookie
							String cookieVal = null;
							String cookieKey = null;
							String sessionCookie = "";
							for (int i = 1; (cookieKey = urlConnection.getHeaderFieldKey(i)) != null; i++) {
								if (cookieKey.equalsIgnoreCase("set-cookie")) {
									cookieVal = urlConnection.getHeaderField(i);
									cookieVal = cookieVal.substring(0, cookieVal.indexOf(";"));
									sessionCookie += cookieVal + ";";
								}
							}
							if (sessionCookie != "") cookieManager.setCookie(downloadFile.url, cookieVal);
						}
						String filename = urlConnection.getHeaderField("Content-Disposition");
						if (filename != null) {
							String[] part = filename.split("filename=");
							if (part.length > 1) {
								filename = part[1].split(";")[0];
								if (!filename.trim().equals("")) {
									downloadFile.path = downloadFile.path.substring(0, downloadFile.path.lastIndexOf("/")) + "/" + filename;
								}
							}
						}
						final File targetFile = getDownloadFile(browser, downloadFile.path);
						if (targetFile == null) {
							mBrowser.exception(getInterfaceName(), "Invalid target path:" + downloadFile.path);
							return;
						}
						FileOutputStream fileOutput = new FileOutputStream(targetFile);
						InputStream inputStream = urlConnection.getInputStream();
						long downloadedSize = 0;
						byte[] buffer = new byte[1024];
						int bufferLength = 0;
						while ((bufferLength = inputStream.read(buffer)) > 0 && !stop) {
							fileOutput.write(buffer, 0, bufferLength);
							downloadedSize += bufferLength;
						}
						inputStream.close();
						fileOutput.close();
						urlConnection.disconnect();
					} catch (SocketTimeoutException ste) {
						mBrowser.exception(getInterfaceName(), "Timeout:" + timeout);
					} catch (Exception e) {
						mBrowser.exception(getInterfaceName(), "Exception:" + e.getLocalizedMessage());
					}
				}
				browser.progressDialog.hide();
				if (finishCallback != null && !finishCallback.trim().equals("")) {
					browser.function.load(finishCallback);
				}
			}
		}.start();
	}

}
